/***************************************************************************
                          sndserver.c  -  description
                             -------------------
    begin                : Tue Apr 16 2002
    copyright            : (C) 2002 by Liming Xie
    email                : liming_xie@yahoo.com

    Modified by:	Thomas Chang
    Date:		2005-04-02
    Version:		2.0.0

    Description:
	1. Improve the performance and patch small defects.
	2. Implement the half-duplex mechanism to avoid the echo (echo canceling is too much for CPU loading)
	3. Remove the drop packet scheme and replace with silence suppression. It can save bandwidth
	   and avoid the sound delay due to the accumulation of sound data to play.
	4. Remove all ALSA version. We only implement sound server based on OSS/Free which is a light weight lib.
	   (ALSA is 3+MB). The price is that we must use duplex sound chip.
	5. Add the sound server and client status monitoring function (CMD handler of command "sound")
	6. <mod_sndsvr> now play less role. It only provide the input device and output device name.
	   Sample rate and channels now is auto set by sound server. In future, we shall just remove
	   the role of <mod_sndsvr> section in core configuration.
	7. Use fast linear interpolation resample mechanism to replace the old resample code
	   which delivers poor audio quality.

 *******************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <linux/soundcard.h>

// #include "ivtypes.h"
// #include "sysconf.h"
#include "utils_sndsvr.h"
#include "utils_str.h"
//#include "srvutils/utils_net.h"
//#include "srvutils/utils_timer.h"
// #include "srvengine/core_log.h"
//#include "srvengine/core_thread.h"
// #include "srvengine/core_cmd.h"
#include "fastresample.h"
#include "utils_net.h"
#include "utils_conf.h"
#include "utils_thread.h"
#include "utils_ptrlist.h"
#include "sndrms.h"
#define __DEBUG
#include "Trace.h"

#define LOG_STATUS 1
#define LOG_ERROR  0
//#define TRACE_DETAIL

typedef struct tagSNDStatistic {
	_longtime	t_connect;		// connect time (when first packet sent)
	_longtime	t_last_calc;		// last time calculate the per sec. data
#ifdef TRACE_DETAIL
	_longtime	t_last_io;
	__u32		t_space[10];		// time space (between each I/O) in usec
	__u64		io_count;		// number of packet I/O
	__u32		ampt_avg;		// data amplitude averaged
	__u32		ampt_now;		// data amplitdue this half sec.
#endif
	__u64		data_total;		// total data sent
	__u32		pkt_accepted;		// total packet accepted
	__u32		pkt_suppressed;		// total packet suppressed
	long		pkt_rms;		// RMS of last packet
//	__u32		data_b2c;		// data samples in last sec.
	double		ksps_avg;		// averavged samples per sec.
//	double		ksps_now;		// this half sec's samples per sec.
} SNDStatistic;

typedef struct tagSNDClient {
	int		sockfd;
	struct pollfd	*poll_entry;

	int		mode;
		// O_RDONLY, O_WRONLY, O_RDWR
	int		sample_rate;
		// 8000 ~ 48000
	int		channels;
		// 1 for mono, 2 for stereo,
		// -1 for left channel, -2 for right channel

	short		*block;
	int		block_size, data_size;
	int		mix_ready;

	resampleContext	*resample;

	/* statistic data */
	SNDStatistic	stat;

} SNDClient;

/* for sound output. we gives small block size to minimized the delay */
#define	SND_BLOCKS_PER_SECOND		50	// 20 msec per block
#define	SND_BLOCK_DRAIN_TIME		100	// msec (time to drain a block just write to device)

typedef struct tagSNDDEVICE {
	char			device_path[ 32 ];
	int			device_index;
	int			snd_handle;		// fd of sound device
	int			mode;
	int			sample_rate, channels;
	/* buffer */
	short			*block;
	int			block_size, data_size;
	PtrList			clients;

	/* I/O threshold */
	DeviceThreshold		*threshold;

	/* control */
	int			closing;
	pthread_t		thread;
	pthread_mutex_t		lock;

	/* device statistic data */
	SNDStatistic	stat;

} SNDDEVICE;

static int		SNDSVR_quit 		= 0;
pthread_mutex_t		SND_lock 		= PTHREAD_MUTEX_INITIALIZER;
PtrList			SND_devices 		= PTRLIST_INITIALIZER;
pthread_t		SND_listen_thread 	= 0;
int			SND_listen_fd		= -1;
int			SND_nb_clients 		= 0;

#define			MAX_SNDSVR_CLIENTS	8
#define			MAX_SAMPLE_RATE		48000
#define			MIN_SAMPLE_RATE		8000

static void SND_update_statistic( SNDStatistic *stat, short *samples, int data_size );

int SND_add_client( int fd )
{
	SNDSVRMSG	msg;
	SNDDEVICE	* device = NULL;
	int		block_size;
	PtrList		*list;
	POSITION	pos = NULL;
	int		ret = -ENODEV;
	int		real_channels;

	// check validation of parameter
	memset( & msg, 0, sizeof(msg) );
	if(	(sock_read_n_bytes(fd, & msg, sizeof(msg)) == sizeof(msg)) &&
		(msg.msg_magic == SNDSVRMSGMAGIC) &&
		(msg.msg_size == sizeof(msg)) &&
		(msg.sample_rate >= MIN_SAMPLE_RATE) &&
		(msg.sample_rate <= MAX_SAMPLE_RATE) &&
		(msg.channels != 0) &&
		(msg.channels >= -2) &&
		(msg.channels <= 2) ) {
		// parameters checked okay
	} else {
		TRACE("[SNDSVR] - connection request error!\n" );
		return -EINVAL;
	}

	pthread_mutex_lock( & SND_lock );
	list = & SND_devices;
	for( pos=list->head; pos!=NULL; pos=pos->next )
	{
		device = (SNDDEVICE *)pos->ptr;
		if( (msg.device_index == device->device_index) &&
		    (msg.mode == device->mode) )
		{
			// send back response
            		real_channels = ((msg.channels==2) ? 2 : 1);
			block_size =  real_channels * msg.sample_rate / SND_BLOCKS_PER_SECOND;
			msg.buf_size = block_size * sizeof(short);

			if( sizeof(msg) == sock_write(fd, & msg, sizeof(msg)) )
			{
				SNDClient * cli = malloc( sizeof(SNDClient) );
				short* buf = malloc( sizeof(short) * block_size );
				if( cli && buf )
				{
					// set the socket to nonblocking mode
					// fcntl(fd, F_SETFL, O_NONBLOCK);
					memset( cli, 0, sizeof(SNDClient) );
					cli->sockfd 		= fd;
					cli->mode 		= msg.mode;
					cli->sample_rate 	= msg.sample_rate;
					cli->channels 		= real_channels;
					cli->block 		= buf;
					cli->block_size 	= block_size;
					cli->data_size 		= 0;
					cli->stat.t_connect	= timeGetLongTime();
					if( cli->mode == O_WRONLY ) {
						// client ==> device
						cli->resample = resampleContext_init (
							msg.channels,
							device->channels,
							msg.sample_rate,
							device->sample_rate );
					} else {
						// device ==> client
						cli->resample = resampleContext_init (
							device->channels,
							msg.channels,
							device->sample_rate,
							msg.sample_rate );
					}
					PtrList_append( & device->clients, cli );
					SND_nb_clients ++;
					ret = 0;
				} else {
					if( cli ) free( cli );
					if( buf ) free( buf );
					TRACE("[SNDSVR]: no memory!\n");
					ret = -ENOMEM;
				}
			} else {
				TRACE("[SMDSVR]: sock write to client error (%d) on sock fd %d\n", errno, fd );
				ret = -EIO;
			}
			break;
		}
	}
	pthread_mutex_unlock( & SND_lock );

	return ret;
}

void SND_close_client( SNDClient * cli )
{
	if( SND_nb_clients > 0 )
	{
		if(cli->sockfd > 0) 	close(cli->sockfd);
		if(cli->block) 		free(cli->block);
		if(cli->resample) 	resampleContext_destroy(cli->resample);
		free(cli);
		SND_nb_clients --;
	}
}

/*
 * listen to each client for sound output (to device) and new client attachment request
 */
void* SND_listen_thread_func( void* param )
{
	struct pollfd		poll_table[ MAX_SNDSVR_CLIENTS + 1 ];
	int			n, fd, ret, rc;
	struct sockaddr_in	fromaddr;
	int			addrlen = sizeof(fromaddr);
	PtrList			*list, *sublist;
	POSITION		pos, subpos, subposCur;
	short			mix_buffer[ MAX_SAMPLE_RATE * 2 / SND_BLOCKS_PER_SECOND ];
#ifdef TRACE_PACKET
	_longtime		t_now;
	char			tstr[32];
#endif
	pthread_mutex_lock( & SND_lock );
	SND_listen_thread = pthread_self();
	pthread_mutex_unlock( & SND_lock );

	while( ! SNDSVR_quit )
	{
		// put the listen fd into poll table
		poll_table[0].fd = SND_listen_fd;
		poll_table[0].events = POLLIN;
		n = 1;

		// put the other connections fd into poll table
		pthread_mutex_lock( & SND_lock );
		list = & SND_devices;
		for( pos=list->head; pos!=NULL; pos=pos->next )
		{
			SNDDEVICE* device = (SNDDEVICE *)pos->ptr;
			sublist = & device->clients;
			for(subpos=sublist->head; subpos!=NULL; subpos=subpos->next )
			{
				SNDClient* cli = (SNDClient *)subpos->ptr;
				if ( cli->mode == O_RDONLY )
					cli->poll_entry = NULL;
				else
				{
					cli->poll_entry = & poll_table[n];
					poll_table[n].fd = cli->sockfd;
					poll_table[n].events = POLLIN;
					n ++;
				}
			}
		}
		pthread_mutex_unlock( & SND_lock );

		// always wait for a event, then accept or read
		do {
			ret = poll( poll_table, n, -1 );
		} while ( (ret == -1) && (! SNDSVR_quit) );
		if ( SNDSVR_quit ) break;
		/* accept new connection */
		if( poll_table[0].revents & POLLIN )
		{
			if ( (fd = accept( poll_table[0].fd, (struct sockaddr *) & fromaddr, & addrlen )) >= 0 )
			{
				if ( SND_nb_clients < MAX_SNDSVR_CLIENTS )
				{
			    		if ( (rc = SND_add_client(fd)) == 0 )
			    		{
						TRACE("[SNDSVR listen thread] - new client added OK!, sockfd = %d\n", fd );
					} else {
						TRACE("[SNDSVR listen thread] - new client add error code %d\n", rc );
						if ( fd >= 0 ) close( fd );
					}
				}
				else
				{
					TRACE("SNDSVR: client number %d reach limit!\n", SND_nb_clients );
					if ( fd >= 0 ) close( fd );
				}
			}
			ret--;
		}
		if ( ret == 0 ) continue;

		// check input from client or clients disconnected
		pthread_mutex_lock( & SND_lock );
		list = & SND_devices;
		/* do for every sound output device */
		for( pos=list->head; pos!=NULL; pos=pos->next )
		{
			SNDDEVICE* device = (SNDDEVICE*)pos->ptr;
			sublist = & device->clients;
			/* obtain data from each clients attached on this device */
			for(subpos=sublist->head; subpos!=NULL; /**/ )
			{
				SNDClient* cli = (SNDClient *)subpos->ptr;
				subposCur = subpos;
				subpos = subpos->next;

				if( cli->poll_entry && (cli->poll_entry->revents & POLLIN) )
				{
					if (cli->data_size < cli->block_size )
					{
						// buffer for sound client is not full,
						// read audio data from client to fill the buffer
						int nbufleft = sizeof(short) * (cli->block_size - cli->data_size);
						int nread = recv(cli->sockfd, & cli->block[ cli->data_size ], nbufleft, 0);
						if( nread > 0 )
						{
							cli->data_size += nread / sizeof(short);
//							TRACE("Clinet==>SNDSVR: %d samples read, %d left\n", cli->data_size, cli->block_size-cli->data_size );
						}
						else if ( (nread == 0) ||
							  (nread<0 && errno!=EAGAIN && errno!=EINTR) )
						{
							// sound client close connection
							PtrList_remove(sublist, subposCur);
							SND_close_client( cli );
							TRACE("Client I/O error - disconnect client!\n");
						}
					}
					cli->mix_ready = cli->data_size >= cli->block_size;
				}
			}
			// mix all clients audio wave to device output buffer then write to sound card
			if( device->mode == O_WRONLY )
			{
				device->data_size = 0;
				memset( device->block, 0, device->block_size*sizeof(short) );
				for(subpos=sublist->head; subpos!=NULL; subpos=subpos->next )
				{
					SNDClient* cli = (SNDClient *)subpos->ptr;
					if( cli->mix_ready  )
					{
						// buffer full, do resample and mixing
						if ( cli->resample )
						{
							SND_resample( cli->resample, cli->block, cli->block_size, mix_buffer, device->block_size );
							SND_mixing( mix_buffer, device->block, device->block_size );
						}
						else
							SND_mixing( cli->block, device->block, device->block_size );
						cli->data_size = 0;
						device->data_size = device->block_size;
						SND_update_statistic( &cli->stat, cli->block, cli->block_size );
					}
				}

				if( device->data_size > 0 )
				{
					if ( device->threshold != NULL &&
					     device->threshold->out_mode == SNDMODE_OUT_OSS &&
					     !RMSW_isSilence( &device->threshold->rms_write, device->block, device->data_size, &device->stat.pkt_rms ) )
					{
						device->stat.pkt_suppressed++;
						TRACE("==>XX: SND out silence supressed!\n" );
					}
					else
					{
						write( device->snd_handle, device->block, device->data_size*sizeof(short) );
						if ( device->threshold )
						{
							device->threshold->t_drain = timeGetLongTime() + SND_BLOCK_DRAIN_TIME;
#ifdef TRACE_PACKET
							fprintf(stderr, "==>OK[%s]: SND out %d (total=%llu)\n",
								time_to_str_ext(timeLongTimeToTime(device->threshold->t_drain),
										timeGetUSec(device->threshold->t_drain),
										DTFMT_TIME|0x03, tstr),
								device->data_size, device->stat.data_total );
						}
						else
						{
							t_now = timeGetLongTime();
							TRACE("==>OO[%s]: Snd out w/o threshold\n",
								time_to_str_ext(timeLongTimeToTime(t_now),
										timeGetUSec(t_now),
										DTFMT_TIME|0x03, tstr) );
#endif
						}
						SND_update_statistic( &device->stat, device->block, device->block_size );
						device->stat.pkt_accepted++;
					}
				}
			}
		}
		pthread_mutex_unlock( & SND_lock );
	}
	SND_listen_thread = 0;
	lprintf( LOG_STATUS, "Service 'SNDSVR' listener stopped.\n" );
	return NULL;
}

/* ------------------------------------------ */

void SND_close_device( SNDDEVICE * device )
{
	SNDClient	* cli;

	lprintf( LOG_STATUS, "\tmod_sndsvr: Close sound device %d: %s\n",
		device->device_index, device->device_path );

	while( PtrList_get_count( & device->clients ) > 0 )
	{
		cli = PtrList_remove_head( & device->clients );
		SND_close_client( cli );
	}

	if( device->snd_handle > 0 ) close( device->snd_handle );
	if( device->block ) free( device->block );
	if ( device->threshold ) SND_destroyDeviceThreshold( device->threshold );
	free( device );
}

typedef struct tagSNDConfig {
	char		device_index[ 16 ];
	char		device_path[ 32 ];
	char		device_mode[ 16 ];
	char		recsrc[ 32 ];
	char		mixer[ 32 ];
} SNDConfig;

int SND_init_mixer( const char * recsrc, const char * mixer )
{
	int		mixer_fd, audio_mode, devnum;
	int		rc = 0;

	if( 0 == strcasecmp( recsrc, "LINE") ) {
		devnum = SOUND_MIXER_LINE;
		audio_mode = O_RDONLY;
	} else if (	0 == strcasecmp( recsrc, "MIC") ) {
		devnum = SOUND_MIXER_MIC;
		audio_mode = O_RDONLY;
	} else {
		devnum = SOUND_MIXER_PCM;
		audio_mode = O_WRONLY;
	}

	// set recording source
	mixer_fd = open( mixer, O_RDWR );
	if( mixer_fd > 0 )
	{
		int volume, tmp1, tmp2;
		if( audio_mode == O_RDONLY )
		{
			tmp1 = (1 << devnum);
			volume = 0;	// turn off mixer output mic. to speaker (local echo)
		        if( (ioctl(mixer_fd, SOUND_MIXER_WRITE_RECSRC, & tmp1) < 0) ||
	    	            (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, & tmp2) < 0) ||
			    (ioctl(mixer_fd, MIXER_WRITE(devnum), & volume) < 0) )
			{
				lprintf( LOG_ERROR, "Mixer device ioctl() failed: '%s'.\n", mixer );
			}
			else if ( tmp1 != tmp2 )
			{
				lprintf( LOG_ERROR, "Mixer device set recording to %s failed!\n", recsrc );
			}
		} else {
			int rightVolume = 100;
			int leftVolume  = 100;
			volume = (rightVolume << 8) | leftVolume;
			if( (ioctl(mixer_fd, SOUND_MIXER_WRITE_VOLUME, & volume) < 0) ||
//			    (ioctl(mixer_fd, SOUND_MIXER_WRITE_SPEAKER, & volume) < 0) ||
			    (ioctl(mixer_fd, SOUND_MIXER_WRITE_PCM, & volume) < 0) )
				lprintf( LOG_ERROR, "Mixer device ioctl() failed: '%s'.\n", mixer );
		}
		close( mixer_fd );
	} else {
		lprintf( LOG_ERROR, "Cannot open mixer device: '%s'.\n", mixer );
		rc = -1;
	}
	return rc;
}

SNDDEVICE * SND_open_device( const char * device_index, SNDConfig * cfg )
{
	int		n_sample_format, n_sample_rate, n_channels;
	int 		block_size = 0;
	int		audio_mode = O_RDONLY;
	int		op_mode;
	SNDDEVICE	* device = NULL;
	short		* buf;

	int 		audio_fd = 0;
	int		devnum, tmp1, tmp2, tmp3;
/*
	if ( 0 == strcasecmp(cfg->recsrc, "LINEDUPLEX") ) {
		devnum = SOUND_MIXER_LINE;
		audio_mode = O_RDWR;
	} else if ( 0 == strcasecmp(cfg->recsrc, "MICDUPLEX") ) {
		devnum = SOUND_MIXER_MIC;
		audio_mode = O_RDWR;
	} else
*/
	if ( 0 == strcasecmp(cfg->recsrc, "LINE") ) {
		devnum = SOUND_MIXER_LINE;
		audio_mode = O_RDONLY;
	} else if ( 0 == strcasecmp(cfg->recsrc, "MIC") ) {
		devnum = SOUND_MIXER_MIC;
		audio_mode = O_RDONLY;
	} else {			// OUT or SPEAKER
		// speaker or other sound output devices
		devnum = SOUND_MIXER_VOLUME;
		audio_mode = O_WRONLY;
	}
	if ( audio_mode == O_RDONLY )
		op_mode = strcasecmp(cfg->device_mode, "HD")==0 ? SNDMODE_IN_HALFDUPLEX : SNDMODE_IN_FULLDUPLEX;
	else
		op_mode = strcasecmp(cfg->device_mode, "NOS")==0 ? SNDMODE_OUT_NOS : SNDMODE_OUT_OSS;

	if ( SND_init_mixer( cfg->recsrc, cfg->mixer ) < 0 )
		return NULL;

	// open sound card device, and set format
	audio_fd = open( cfg->device_path, audio_mode );
	if( audio_fd > 0 )
	{
		n_sample_format = AFMT_S16_LE;
		/*
		 * sound chip or driver has huge buffer (around 32K) and only return to AP when it is fill-up
		 * Using high sample rate can minimized the delay of buffer-filling time.
		 * We need to resample the data to lower rate.
		 */
		n_sample_rate = 48000;		// this is default sample rate for device. Unless device does not support this
		n_channels = 2;			// set as stereo input so that client can select mono, left-only, right-only and stereo sound

	   	tmp1 = AFMT_S16_LE;
    		tmp2 = n_channels;
	    	tmp3 = n_sample_rate;
    		if( ioctl( audio_fd, SNDCTL_DSP_SETFMT, 	&tmp1 ) >= 0 &&
     		    ioctl( audio_fd, SNDCTL_DSP_CHANNELS, 	&tmp2 ) >= 0 &&
       		    ioctl( audio_fd, SNDCTL_DSP_SPEED, 		&tmp3 ) >= 0 )
       		{
       			/* get sample rate and channels from driver. To be sure we all working on same basis as driver does */
			n_channels = tmp2;
	    		n_sample_rate = tmp3;
	    		// init for opened device
		    	block_size = n_sample_rate * n_channels / SND_BLOCKS_PER_SECOND;
			device = malloc( sizeof(SNDDEVICE) );
			buf = malloc( sizeof(short) * block_size );
			if( device && buf )
			{
				memset( device, 0, sizeof(SNDDEVICE) );
				pthread_mutex_init( & device->lock, NULL );
//				device->mode = audio_mode==O_RDONLY) || (audio_mode==O_RDWR)) ? O_RDONLY : O_WRONLY;
				device->mode = audio_mode;
				device->device_index = strtol(device_index, NULL, 0);
				strcpy( device->device_path, cfg->device_path );
				device->sample_rate = n_sample_rate;
				device->channels = n_channels;
				device->block = buf;
				device->block_size = block_size;
				device->data_size = 0;
				device->snd_handle = audio_fd;
				device->stat.t_connect = timeGetLongTime();
				device->threshold = SND_getDeviceThreshold( device->device_path, device->device_index, audio_mode, op_mode );
				if ( device->threshold == NULL )
					lprintf( LOG_ERROR, "%s (%s) DeviceThreshold init error. Silence suppression will not work!\n",device->device_path, cfg->recsrc );
				lprintf( LOG_STATUS, "Open sound device %d: %s for %s in %s mode at (%d,%d,%s).\n",
					device->device_index,
					device->device_path,
					device->mode==O_RDONLY ? "RDONLY" : (device->mode==O_WRONLY ? "WRONLY" : "RDWR"),
					op_mode ? "half-duplex" : "full-duplex",
					device->sample_rate, device->channels, cfg->recsrc );
			} else {
				if(device) 	free(device);
				if(buf) 	free(buf);
				device = NULL;
			}
		}
		else
		{
   			close(audio_fd);
			lprintf( LOG_ERROR, "  Cannot open '%s': %s.\n", cfg->device_path, strerror(errno) );
		}
    	}
	return device;
}

static void SND_update_statistic( SNDStatistic *stat, short *sample, int data_size )
{
	_longtime	tnow_msec = timeGetLongTime();
#ifdef TRACE_DETAIL
	int	i;
	__u64	t_space;
	__u32	avg;
#endif
	stat->data_total += data_size;
//	stat->data_b2c += data_size;
	if ( stat->t_last_calc == 0 )
		stat->t_last_calc = tnow_msec;
#ifdef TRACE_DETAIL
	t_space = tnow_msec - stat->t_last_io;
	for( i=9; i>0; i--)
		stat->t_space[i] = stat->t_space[i-1];
	stat->t_space[0] = t_space;
	stat->t_last_io = tnow_mec;
	stat->io_count++;
	for( avg=0, i=0; i<data_size; i++, avg+=sample[i] );
	stat->ampt_avg = (avg + stat->data_total * stat->ampt_avg)/(data_size + stat->data_total);
	stat->ampt_now = (avg + stat->data_b2c * stat->ampt_now)/(data_size + stat->data_b2c);
#endif
	/* update sample rate on every 500 ms */
	if ( tnow_msec - stat->t_last_calc > 500 )
	{
		stat->ksps_avg = stat->data_total * 1000;
		stat->ksps_avg /= (tnow_msec - stat->t_connect);
//		stat->ksps_now = stat->data_b2c * 1000;
//		stat->ksps_now /= (tnow_us - stat->t_last_calc);
//		stat->data_b2c = 0;
		stat->t_last_calc = tnow_msec;
	}
}


void* SND_input_thread_func( void * arg )
{

	SNDDEVICE 	* device = arg;
	int		nread, rc;
	PtrList		* list = & device->clients;
	POSITION	pos, pos_this;
	SNDClient	* cli;
	_longtime	t_read;
#ifdef TRACE_PACKET
	char		tstr[32];
	unsigned long	silent_count = 0;
	unsigned long	skip_by_write = 0;
#endif
	pthread_mutex_lock( & SND_lock );
	device->thread = pthread_self();
	pthread_mutex_unlock( & SND_lock );

	while( ! device->closing )
	{
		// read data from sound card device file
		do {
			nread = read( device->snd_handle, device->block, sizeof(short) * device->block_size );
		} while ( nread == -EAGAIN && device->closing==0);

		if ( device->closing ) break;

		if( nread > 0 ) {
			device->data_size = nread >> 1;
		} else {
			device->data_size = 0;
			lprintf( LOG_ERROR, "Error recording from sound device %s, errno=%d\n", device->device_path, errno );
			break;
		}
		/* update the per device statistic */
		SND_update_statistic( &device->stat, device->block, device->data_size );
		t_read = timeGetLongTime();

		/* check for silence -- suppress it */
		if ( device->threshold )
		{
			device->threshold->t_read = t_read;
			if ( RMSW_isSilence( &device->threshold->rms_read, device->block, device->data_size, &device->stat.pkt_rms ) )
			{
#ifdef TRACE_PACKET
				TRACE("<==SS[%s]: Skip silence samples %lu\n",
					time_to_str_ext(timeLongTimeToTime(t_read),
							timeGetUSec(t_read),
							DTFMT_TIME|0x04, tstr ),
					++silent_count );
#endif
				device->stat.pkt_suppressed++;
				continue;
			}
			/* check for half-duplex and there is sound output now... */
			if ( device->threshold->in_mode == SNDMODE_IN_HALFDUPLEX &&
			     device->threshold->t_read < device->threshold->t_drain )
			{
#ifdef TRACE_PACKET
				TRACE("<==XX[%s]: Input suppressed by output (%lu)\n",
					time_to_str_ext(timeLongTimeToTime(t_read),
							timeGetUSec(t_read),
							DTFMT_TIME|0x04, tstr ),
					++skip_by_write );
#endif
				device->stat.pkt_suppressed++;
				continue;
			}
		}
		device->stat.pkt_accepted++;
#ifdef TRACE_PACKET
		TRACE("<==OK[%s]: Input accepted\n",
				time_to_str_ext(timeLongTimeToTime(t_read),
						timeGetUSec(t_read),
						DTFMT_TIME|0x04, tstr ) );
#endif
		// resample and output to every clients attached on me
		pthread_mutex_lock( & device->lock );
		for(pos=list->head; pos!=NULL; )
		{
			cli = (SNDClient *)pos->ptr;
			pos_this = pos;
			pos = pos->next;
			if( cli->mode == O_WRONLY ) continue;
			if ( cli->resample == NULL )
			{
				rc = write( cli->sockfd, device->block, sizeof(short) * device->data_size );
				/* update the per client statistic data */
				SND_update_statistic( &cli->stat, device->block, device->data_size );
			}
			else
			{
				// resample is required
				SND_resample( cli->resample, device->block, device->data_size, cli->block, cli->block_size );
				rc = write( cli->sockfd, cli->block, sizeof(short) * cli->block_size );
				/* update the per client statistic data */
				SND_update_statistic( &cli->stat, cli->block, cli->block_size );
			}
			if ( rc < 0 )
			{
				fprintf(stderr, "[sndsvr input thread] - client socket %d send error (%d). remove it!\n", cli->sockfd, errno );
				PtrList_remove(list, pos_this);
				SND_close_client( cli );
			}
		}
		pthread_mutex_unlock( & device->lock );
	}
	TRACE("\tSound device %s input thread break out and going to close the device...\n", device->device_path );
	pthread_mutex_lock( & SND_lock );
	if ( (pos = PtrList_find( & SND_devices, device )) != NULL )
	{
		PtrList_remove( & SND_devices, pos );
		SND_close_device( device );
	}
	pthread_mutex_unlock( & SND_lock );

	return NULL;
}

static int		PA_listen_fd = 0;
static pthread_t	PA_thread_id = 0;

#define			PA_PACKET_SIZE		1024
#define			PA_HEADER_SIZE		12

/* thread listening the PA UDP port and send to sound server */

#define UMFSND_SAMPLERATE	8000
#define UMFSND_CHANNELS		8

void* PA_service_thread_func( void * arg )
{
	int		fd = (int) arg;
	int		nr, nw;
	HSNDSVR * 	sndsvr = NULL;
	char 		buf[ PA_PACKET_SIZE ];
	int 		retval;
	fd_set 		rfds;
	struct timeval 	tv;

	pthread_mutex_lock( & SND_lock );
	PA_thread_id = pthread_self();
	pthread_mutex_unlock( & SND_lock );
	sndsvr = SNDSVR_open( 0, UMFSND_SAMPLERATE, UMFSND_CHANNELS, O_WRONLY );
	if( sndsvr )
	{
		TRACE("[PA listener] - ready. Listen on sock fd %d\n",fd );
		do {
			FD_ZERO( & rfds );
			FD_SET( fd, & rfds );
			tv.tv_sec = 1;
			tv.tv_usec = 0;
//			if ( (retval = select( fd+1, & rfds, NULL, NULL, & tv )) < 0 )
			if ( (retval = select( fd+1, & rfds, NULL, NULL, NULL )) < 0 )
			{
				TRACE("{PA listener] - select error. stop!\n");
				break;
			}
			else if( retval > 0 )
			{
				// receive data from UDP socket
				if ( (nr=recv( fd, buf, PA_PACKET_SIZE, MSG_NOSIGNAL )) <= 0 ||
				     (nw=SNDSVR_write( sndsvr, buf + PA_HEADER_SIZE, nr - PA_HEADER_SIZE )) != nr-PA_HEADER_SIZE )
				{
					TRACE("[PA listener] - core engine close PA port or sound module close connection. quit PA!\n");
					break;
				}
			}  // 0 -- timed out no data
			else
			{
				TRACE("{PA listener] - select nothing!\n");
				continue;
		}
		} while ( ! SNDSVR_quit );

		SNDSVR_close( sndsvr );
		sndsvr = NULL;
	}
	else
		TRACE("[PA listener] - Sound Server is not ready. Quit the job.\n" );
	PA_thread_id = 0;

	return NULL;
}

#if 0
static void SND_show_statistic( SNDStatistic *stat, int channels, int is_device, FILE *fp )
{
	int	nc = channels == 2 ? 2 : 1;
	double	rate_accepted, rate_suppressed;
	__u32	pkt_total = stat->pkt_accepted + stat->pkt_suppressed;
#ifdef TRACE_DETAIL
	char	tstr1[32], tstr2[32];
	__u32	t_avgspace = 0;
	int	i;
	__u64	t_spane = timeGetLongTime() - stat->t_connect;
	double	io_rate = stat->io_count * 1000;

	io_rate /= t_spane;
	time_to_str_ext( timeLongTimeToTime(stat->t_connect), timeGetUSec(stat->t_connect), DTFMT_DATETIME|0x03, tstr1 );
	time_to_str_ext( timeLongTimeToTime(stat->t_last_io), timeGetUSec(stat->t_last_io), DTFMT_DATETIME|0x03, tstr2 );
	for( i=0; i<10; i++)
		t_avgspace += stat->t_space[i];
	t_avgspace /= 10;

	fprintf( fp,
		"\t Connection time : %s\n"
		"\t Last I/O time   : %s\n"
		"\t time spaces     : %u %u %u %u %u %u %u %u %u %u\n"
		"\t averaged t_space: %u\n"
		"\t total data I/O  : %llu 16-bit word\n"
		"\t                 : %llu I/O in %llu usec (%.2f time/sec)\n"
		"\t averaged ampt.  : %8u (overall) %8u (recently)\n"
		"\t averaged s_rate : %8.2f\n",
		tstr1, tstr2,
		stat->t_space[0], stat->t_space[1], stat->t_space[2], stat->t_space[3], stat->t_space[4],
		stat->t_space[5], stat->t_space[6], stat->t_space[7], stat->t_space[8], stat->t_space[9],
		t_avgspace,
		stat->data_total,
		stat->io_count, t_spane, io_rate,
		stat->ampt_avg, stat->ampt_now,
		stat->ksps_avg/nc
	);
#else
	rate_accepted   = ((double)stat->pkt_accepted) * 100 / pkt_total;
	rate_suppressed = ((double)stat->pkt_suppressed) * 100 / pkt_total;

	fprintf( fp,
		"\t Connection time  : %s\n"
		"\t total data I/O   : %llu 16-bit word\n"
		"\t averaged s_rate  : %8.2f\n",
		time_to_str_ext( timeLongTimeToTime(stat->t_connect), timeGetUSec(stat->t_connect), DTFMT_DATETIME|0x03, NULL ),
		stat->data_total,
		stat->ksps_avg/nc
	);
	if ( is_device )
	{
		fprintf( fp,
		"\t packet accepted  : %u (%.2f)\n"
		"\t packet suppressed: %u (%.2f)\n"
		"\t RMS last packet  : %.2f%%\n",
		stat->pkt_accepted,   rate_accepted,
		stat->pkt_suppressed, rate_suppressed,
		(double)stat->pkt_rms/100
		);
	}
#endif
}

#define SHOW_READ	0x01
#define SHOW_WRITE	0x02
#define SHOW_CLIENT	0x04
#define SHOW_READWRITE	0x03
#define SHOW_ALL	0x07

static int SND_CMD_show( CMDContext *cc, int argc, char* argv[] )
{
	POSITION	pos_dev, pos_cli;
	SNDDEVICE 	* device;
	SNDClient	* client;
	int		n, show_flag;
	struct sockaddr_in	addr;
	socklen_t	addrlen = sizeof(addr);
	char		ip[32];
	double		silence_threshold = 0.;
	_longtime	t_client_io=0;

	show_flag = SHOW_CLIENT;
	for (n=1; n<argc; n++)
	{
		if ( !strcasecmp(argv[n], "-c") )
			show_flag &= ~SHOW_CLIENT;
		else if ( !strcasecmp(argv[n], "r") )
			show_flag |= SHOW_READ;
		else if ( !strcasecmp(argv[n],"w") )
			show_flag |= SHOW_WRITE;
	}
	if ( (show_flag & (SHOW_READWRITE)) == 0 )
		show_flag |= (SHOW_READWRITE);

	for( pos_dev=SND_devices.head; pos_dev!=NULL; pos_dev=pos_dev->next )
	{
		device = (SNDDEVICE *)pos_dev->ptr;
		if ( device->mode==O_RDONLY && device->threshold )
		{
			silence_threshold = (double)RMSW_threshold(&device->threshold->rms_read);
			t_client_io = device->threshold->t_read;
		}
		else if ( device->mode==O_WRONLY && device->threshold )
		{
			silence_threshold = (double)RMSW_threshold( &device->threshold->rms_write );
			t_client_io = device->threshold->t_drain;
		}
		if ( !((show_flag & SHOW_READ)  && device->mode==O_RDONLY) &&
		     !((show_flag & SHOW_WRITE) && device->mode==O_WRONLY) )
			continue;

		silence_threshold /= 100;
		fprintf( cc->fpout,
			"Sound Device %s:\n"
			"  mode             : %s\n"
			"  sample rate      : %d\n"
			"  channels         : %d\n"
			"  block size       : %d (16-bit word)\n"
			"  silence threshold: %.2f%%\n"
			"  last client I/O  : %s\n"
			"  client(s)        : %d\n"
			"  [I/O statistics]\n",
			device->device_path,
			device->mode==O_RDONLY ? "Read" : "Write",
			device->sample_rate,
			device->channels,
			device->block_size,
			silence_threshold,
			t_client_io==0 ? "(no)" : time_to_str_ext( timeLongTimeToTime(t_client_io), timeGetUSec(t_client_io), DTFMT_TIME|0x03, NULL ),
			device->clients.count );
		SND_show_statistic( &device->stat, device->channels, 1, cc->fpout );

		if ( !(show_flag & SHOW_CLIENT) ) continue;

		for( n=0,pos_cli=device->clients.head; pos_cli!=NULL; pos_cli=pos_cli->next,n++ )
		{
			client = (SNDClient *)pos_cli->ptr;
			getpeername( client->sockfd, (struct sockaddr*) & addr, & addrlen );
			IP_to_str( addr.sin_addr.s_addr, ip );
			fprintf( cc->fpout,
				"  Client %d   : %s\n"
				"    mode      : %s\n"
				"    s_rate    : %d\n"
				"    channels  : %d\n"
				"    block size: %d (16-bit word)\n"
				"    [I/O statistics]\n",
				n, ip,
				client->mode==O_RDONLY ? "Read" : "Write",
				client->sample_rate,
				client->channels,
				client->block_size );
			SND_show_statistic( &client->stat, client->channels, 0, cc->fpout );
		}
	}
	return 0;
}
#endif

// int SND_initialize( ConfUnit * mod_conf )
int SND_initialize()	// void *mod_conf )
{
	ConfUnit	* subcf;
	PtrList		* list;
	StrStrPair	* item;

	pthread_t	thread;
	POSITION	pos;
	char 		* q;
	SNDConfig 	tempcfg;

	SNDDEVICE	*device;
	int		nb_devices = 0;
	// int		SoundPort = ConfUnit_get_item_as_int(mod_conf, STR_SNDPORT, DEF_SNDPORT);
	// int		PAPort = ConfUnit_get_item_as_int(mod_conf, STR_PAPORT, DEF_PAPORT);

//	int		SoundPort = DEF_SNDPORT;	// ConfUnit_get_item_as_int(mod_conf, STR_SNDPORT, DEF_SNDPORT);
//	int		PAPort = DEF_PAPORT;		//ConfUnit_get_item_as_int(mod_conf, STR_PAPORT, DEF_PAPORT);

	int		SoundPort = 12345;	// ConfUnit_get_item_as_int(mod_conf, STR_SNDPORT, DEF_SNDPORT);
	int		PAPort = 23456;		//ConfUnit_get_item_as_int(mod_conf, STR_PAPORT, DEF_PAPORT);

	/* find all the sound devices in config file,
	 * and merge the config of the same device together
	 */

	//	if(! subcf) return -EINVAL;
	// modify
	// from here we need to fake a confiuration
	// define default configuration

	list = & subcf->items;
	for( pos=list->head; pos!=NULL; pos=pos->next )
	{
		item = (StrStrPair *)pos->ptr;

		memset( & tempcfg, 0, sizeof(SNDConfig) );
		if( strgetwordex( item->value, tempcfg.device_path, 32, ',', &q ) &&
			strgetwordex( q, tempcfg.device_mode, 16, ',', &q ) &&
			strgetwordex( q, tempcfg.recsrc, 32, ',', &q ) &&
			strgetword( q, tempcfg.mixer, 32, NULL ) ) {

			device = SND_open_device( item->key, & tempcfg );
			if( device )
			{
				PtrList_append( & SND_devices, device );
				if( device->mode == O_RDONLY )
				{
					/* create input thread */
					if( 0 == THREAD_create( & thread, NULL, SND_input_thread_func, device, "sound input") ) {
						nb_devices ++;
					} else {
						PtrList_remove_rear( & SND_devices );
						SND_close_device( device );
					}

				}
			}
		}
	}

	if( SoundPort > 0 )
	{
		SND_listen_fd = sock_listen( SoundPort, NULL, 5 );
		if( (SND_listen_fd >= 0) &&
			(0 == THREAD_create(& thread, NULL, SND_listen_thread_func, (void*)SND_listen_fd, "sound server listener")) ) {
			lprintf( LOG_STATUS, "Service 'SNDSVR' ready (port: %d).\n", SoundPort );
		} else {
			close(SND_listen_fd);
			SND_listen_fd = -1;
			lprintf( LOG_ERROR, "Sound service cannot listen at port %d: %s\n", SoundPort, strerror(-SND_listen_fd) );
		}
	}

	if( PAPort > 0 && SND_listen_fd >= 0 )
	{
		struct sockaddr_in 	my_addr;
		int 			fd, tmp;
		// PA service
		fd = socket( AF_INET, SOCK_DGRAM, 0 );
		if( fd > 0 ) {
		    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));
		    memset(&my_addr, 0, sizeof(my_addr));
		    my_addr.sin_family = AF_INET;

		    my_addr.sin_port = htons ( PAPort );
		    my_addr.sin_addr.s_addr = htonl (INADDR_ANY);

		    if (bind (fd, (struct sockaddr *) &my_addr, sizeof (my_addr)) >= 0) {
		    	PA_listen_fd = fd;
		    } else {
		    	close( fd );
		    	fd = 0;
		    }
		}

		if( (PA_listen_fd >= 0) &&
		    (0 == THREAD_create(& thread, NULL, PA_service_thread_func, (void*)PA_listen_fd, "PA receiver")) )
		{
			lprintf( LOG_STATUS, "Service 'PA' ready (port: %d).\n", PAPort );
		}
		else
		{
			close( PA_listen_fd );
			PA_listen_fd = 0;
			lprintf( LOG_ERROR, "PA service cannot listen at port %d.\n", PAPort );
		}
	}
#if 0
	CMD_register( "sound", SND_CMD_show,
		"sound [r|w] [-c]: display sound server and client statistic data\n"
		" [r|w] select sound reader or sound writer. Default is both\n"
		" -c    suppress the client statistic information\n");
#endif
	return 0;
}

int SND_terminate( ConfUnit * mod_conf )
{
	SNDDEVICE 	*device;
	POSITION	pos;

	TRACE("[mod_sndsvr] - Phase I terminating start...\n");
	SNDSVR_quit = 1;
	// CMD_unregister( SND_CMD_show );

	// stopping sound server
	if( SND_listen_thread )
		pthread_kill( SND_listen_thread, SIGINT );

	if( SND_listen_fd >= 0 )
	{
		lprintf( 0, "\tmod_sndsvr: Service 'SNDSVR' closed.\n" );
		close( SND_listen_fd );
		SND_listen_fd = -1;
	}

	// stopping PA service
	if( PA_thread_id )
		pthread_kill( PA_thread_id, SIGINT );

	if( PA_listen_fd >= 0 )
	{
		lprintf( LOG_STATUS, "\tmod_sndsvr: Service 'PA' stopped.\n" );
		close( PA_listen_fd );
		PA_listen_fd = -1;
	}

	// Notice all device input threads to terminate
	pthread_mutex_lock( & SND_lock );
	for (pos=SND_devices.head; pos!=NULL; pos=pos->next )
	{
		device = (SNDDEVICE *)pos->ptr;
		if ( device != NULL && device->mode == O_RDONLY )
		{
			device->closing = 1;
			TRACE("\tnotice device %s input thread to close!\n", device->device_path );
			pthread_kill( device->thread, SIGINT );
		}
	}
	pthread_mutex_unlock( & SND_lock );
	TRACE("[mod_sndsvr] - phase I termination done!\n" );
	return 0;
}

int SND_post_terminate( void )
{
	SNDDEVICE 	*device;

	TRACE("[mod_sndsrv] - phase II termination start...\n");

	if( SND_listen_thread )
	{
		usleep( 10000 );
		TRACE("\tSound listen thread not exit. Wait for it!\n" );
		THREAD_cancel( SND_listen_thread );
		SND_listen_thread = 0;
	}

	if( PA_thread_id )
	{
		usleep( 10000 );
		TRACE("\tPA thread not exit. Wait for it!\n" );
		THREAD_cancel( PA_thread_id );
		PA_thread_id = 0;
	}

	while( SND_devices.count > 0 )
	{
		pthread_mutex_lock( & SND_lock );
		device = PtrList_remove_head( & SND_devices );
		pthread_mutex_unlock( & SND_lock );
		if ( device != NULL && device->mode == O_RDONLY )
		{
			TRACE("\tsound input device %s not finished. Close it by myself!\n", device->device_path );
			THREAD_cancel( device->thread );
			SND_close_device( device );
		}
	}
	pthread_mutex_destroy( &SND_lock );

	TRACE("[mod_sndsvr] - phase II module termination done\n");

	return 0;
}

